'''
Created on Nov 25, 2010

@author: dexen
'''

import random

from dexen.system.common.meta import TaskMeta
from dexen.system.common.states import TaskState
from dexen.system.common.transact import SlaveRecvTransact, SlaveSendTransact,\
    MasterRecvTransact, RecvTransact, Transaction
from dexen.system.common.base import Individual
from dexen.system.node.master.mgrs.ind_mgr import IndMgr


class TaskData(object):
    def __init__(self, task_meta):
        self.task_meta = task_meta
        self.pending_ids = set()
        self.processing_ids = set()
        self.n_successes = 0
        self.n_fails = 0
        self.tot_exec_time = 0
    
    def enough_data(self):
        return len(self.pending_ids) >= self.task_meta.get_input_size()
    
    def get_ids(self):
        task_meta = self.task_meta
        assert isinstance(task_meta, TaskMeta)
        assert self.enough_data()
        
        ids = []
        for _ in range(task_meta.get_input_size()):
            id = self.pending_ids.pop()
            ids.append(id)
            assert id not in self.processing_ids
            self.processing_ids.add(id)
        return ids
    
    def synch_pending(self, ind):
        assert isinstance(ind, Individual)
        task_meta = self.task_meta
        assert isinstance(task_meta, TaskMeta)        
        valid_func = task_meta.get_valid_func()
        if valid_func(ind) and ind.__id__ not in self.processing_ids:
            self.pending_ids.add(ind.__id__)
        elif ind.__id__ in self.pending_ids:
            self.pending_ids.remove(ind.__id__)
    
    def synch_processing(self, ind):
        assert isinstance(ind, Individual)
        assert ind.__id__ in self.processing_ids
        #if ind.__id__ in self.processing_ids:
        self.processing_ids.remove(ind.__id__)
    
    def is_in_pending(self, ind):
        assert isinstance(ind, Individual)
        return ind.__id__ in self.pending_ids

    def is_in_processing(self, ind):
        assert isinstance(ind, Individual)
        return ind.__id__ in self.processing_ids
    
    def get_state(self):
        task_meta = self.task_meta
        assert isinstance(task_meta, TaskMeta)
        task_state = TaskState(task_meta.name, task_meta.label)
        task_state.n_fails = self.n_fails
        task_state.n_successes = self.n_successes
        task_state.n_pending_inds = len(self.pending_ids)
        task_state.n_processing_inds = len(self.processing_ids)
        task_state.tot_exec_time = self.tot_exec_time
        return task_state
        

class TaskMgr(object):
    def __init__(self, ind_mgr):
        self.tasks_data = {}
        self.ind_mgr = ind_mgr
        self.inact_ids = set()
        self.act_ids = set()
        self.task_names = [] # used to keep the order of task in which they are started
                
    def start_task(self, class_obj, label, input_size, valid_func):
        """Create TaskMeta and TaskState classes
        Initialise an input set for the task.
        """
        task_meta = TaskMeta(class_obj, label, input_size, valid_func)
        task_name = task_meta.get_name() 
        self.tasks_data[task_name] = TaskData(task_meta)
        self.task_names.append(task_name)
    
    def stop_task(self, class_obj):
        task_name = TaskMeta.infer_name(class_obj)
        return task_name
        
    def avail_task(self):
        """Go through the list of TaskData(s)
        Return a task_name if it has enough input 
        """
        tasks = self.tasks_data.values()
        random.shuffle(tasks)
        for task_data in tasks:
            assert isinstance(task_data, TaskData)
            if task_data.enough_data():
                return task_data.task_meta
        return None
    
    def get_inds(self, task_name):
        """Get as many inds for task_name as it requires.
        Update its corresponding active id set.
        """
        task_data = self.tasks_data[task_name]
        assert isinstance(task_data, TaskData)
        assert task_data.enough_data()
        inds = []        
        for id in task_data.get_ids():
            inds.append(self.ind_mgr.get_ind(id))
        return inds
                     
    def get_inactive_ids(self):
        res = self.inact_ids
        self.inact_ids = set()
        return res
    
    def get_reactive_ids(self):
        res = self.act_ids
        self.act_ids = set()
        return res
    
    def update_task(self, transact):
        assert isinstance(transact, SlaveRecvTransact)
        task_data = self.tasks_data[transact.get_task_name()]
        assert isinstance(task_data, TaskData)
        if transact.has_task_succeeded():
            task_data.n_successes += 1
            task_data.tot_exec_time += transact.get_exec_time()
        else:
            task_data.n_fails += 1

    def sync_all_tasks(self, transact):
        assert isinstance(transact, Transaction)
        assert isinstance(transact, RecvTransact)
        for ind in transact.get_inds():
            assert isinstance(ind, Individual)
            is_active = False
            was_active = False
            for task_data in self.tasks_data.values():
                assert isinstance(task_data, TaskData)
                if task_data.is_in_pending(ind) or task_data.is_in_processing(ind):
                    was_active = True
                task_data.synch_pending(ind)
                if task_data.is_in_pending(ind) or task_data.is_in_processing(ind):
                    is_active = True
            if not is_active:
                self.inact_ids.add(ind.__id__)
            if not was_active and is_active:
                self.act_ids.add(ind.__id__)
            
    def sync_task_processing(self, transact):
        assert isinstance(transact, SlaveSendTransact)
        task_data = self.tasks_data[transact.get_task_name()]
        assert isinstance(task_data, TaskData)
        for ind in transact.get_inds():
            task_data.synch_processing(ind)

    def update_master(self, transact):
        assert isinstance(transact, MasterRecvTransact)
        self.sync_all_tasks(transact)
        
    def update_slave(self, transact, sent_transact):
        assert isinstance(transact, SlaveRecvTransact)
        assert isinstance(sent_transact, SlaveSendTransact)    
        self.sync_task_processing(sent_transact)
        transact.combine(sent_transact)
        self.sync_all_tasks(transact)
        self.update_task(transact)
    
    def recover_slave(self, transact, sent_transact):
        assert isinstance(transact, SlaveRecvTransact)
        assert isinstance(sent_transact, SlaveSendTransact)    
        self.sync_task_processing(sent_transact)
        transact.combine(sent_transact)
        self.sync_all_tasks(transact)
      
    def get_task_states(self):
        task_states = []
        for task_name in self.task_names:
            task_data = self.tasks_data[task_name]
            assert isinstance(task_data, TaskData)
            task_states.append(task_data.get_state())
        return task_states
    
    def __repr__(self):
        task_states = self.get_task_states()
        out = "------------------ TaskMgr ------------------\n"
        for task_state in task_states:
            assert isinstance(task_state, TaskState)
            out += task_state.__repr__()
        out += "---------------------------------------------\n"
        return out
    
    